package uk.ac.cam.juliet.zedcat.socialnetworking;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import uk.ac.cam.juliet.zedcat.db.Account;
import uk.ac.cam.juliet.zedcat.db.Sighting;
import uk.ac.cam.juliet.zedcat.db.dbAnalysisDatabase;
import uk.ac.cam.juliet.zedcat.socialnetworking.plugin.EmailPlugin;
import uk.ac.cam.juliet.zedcat.socialnetworking.plugin.FacebookPlugin;
import uk.ac.cam.juliet.zedcat.socialnetworking.plugin.PingFMPlugin;
import uk.ac.cam.juliet.zedcat.socialnetworking.plugin.TwitterPlugin;

/**
 *  A class which links the analysis to Tom Alexander's dbAnalysisDatabase.
 *  @author Daniel Wright
 */

public class DatabaseController {

	private dbAnalysisDatabase db = null; // Tom Alexander's database interface.
	private PluginManager pm = new PluginManager(); // The plugin manager updates are pushed to.
	private Timestamp lstProcSightTime; // The timestamp of the last processed sighting.
	private String catName = null; // The name of the cat.
	private boolean catMale = false; // The gender of the cat.
	private Logger lg;
	private Hashtable<String, Timestamp> tblLastEntryTimestamps;
	private Hashtable<String, Boolean> tblWaitingForEntry;
	private Stats stats;
	
	public DatabaseController(String address, String username, String password, Logger lg) throws ClassNotFoundException, SQLException {
		this.db = new dbAnalysisDatabase(address,username,password);
		this.lg = lg;
		this.stats = new Stats(db,lg);
		tblLastEntryTimestamps = new Hashtable<String, Timestamp>();
		tblWaitingForEntry = new Hashtable<String,Boolean>();
	}
	
	
	public void pollForSettings() throws SQLException {
		connect();
		lg.log("Connecting to Database.");
		
		this.catName = db.getSettingsEntryForKey("Name");
		lg.log("Cat Name = " + catName);
		
		this.catMale = db.getSettingsEntryForKey("Gender").equals("Male") ? true : false;
		lg.log("Cat Male = " + catMale);
		
		boolean pollforeverything = false;  // Set this true if you want to initially poll for everything in the database.
		
		if(pollforeverything){
				lg.log("Timestamp epoch'd");
				this.lstProcSightTime = new Timestamp(0);
		} else {
			if(db.getSettingsEntryForKey("Processed") == null) {
				lg.log("No \"Processed\" entry in database settings table");
				this.lstProcSightTime = new Timestamp(0);
			} else {
				this.lstProcSightTime = Timestamp.valueOf(db.getSettingsEntryForKey("Processed"));
			}
		}
		
		
		lg.log("Last sighting = " + lstProcSightTime);
		lg.setTimeStamp(lstProcSightTime);
		try{
			lg.log("Downloading statistics.");
		stats.downloadStats();
		} catch(NullPointerException e1) {
			lg.log("Resetting statistics");
			stats.resetStats();
		}
		lg.log("Disconnecting from database.");
		disconnect();
		refreshCredentials(); // Comment out if you don't want to push updates when testing.
	}
	
	public void connect() throws SQLException {
			db.connect();
	}
	
	public void disconnect() throws SQLException {
			db.closeConnection();
	}
	
	public void poll() throws SQLException {
		connect();
		
		String cn = db.getSettingsEntryForKey("Name");
		if(!cn.equals(catName)){catName=cn;lg.log("Cat Name = " + catName);}
		boolean cm = db.getSettingsEntryForKey("Gender").equals("Male") ? true : false;
		if(cm!=catMale){catMale=cm;lg.log("Cat Male = " + catMale);}
		
		List<Sighting> l = null;
		lg.log("Query for sightings since " + lstProcSightTime);
		l = db.getSightingsSince(lstProcSightTime);

		if (l == null) {
			lg.log("No sightings found.");
		} else {
			Iterator<Sighting> itr = l.iterator();
			while (itr.hasNext()) {
				// For each sighting, generate an update message and post it.
				boolean allow = true;
				Sighting si = itr.next();
				
				lg.log("*New instance. sightingID=" + si.getSightingID()
						+ " Timestamp=" + si.getTime().toString() + " AreaID="
						+ si.getActionID() + " Entry=" + si.isEventEntry());
				
				// Are we currently waiting for an entry or exit event?
				// First check to make sure that we have seen this action before.
				if(tblWaitingForEntry.containsKey(si.getActionID())) {
					// The key is contained. Check the value to make sure that it corresponds to what we want.
					if(si.isEventEntry() == tblWaitingForEntry.get(si.getActionID())) {
						// Waiting for this type. Let it through.
						lg.log("Entry/exit test passed.");
					} else {
						lg.log("# Entry/exit failed.");
						allow = false;
					}
				} else {
					// We have not seen this action before. 
					// Set a key in the table for this action, and set looking for entry true.
					tblWaitingForEntry.put(si.getActionID(), Boolean.TRUE);
				}
				
				// If the event is an entry, only let it through if we have not had an entry of the same type in the last 5 minutes.
				
				if(si.isEventEntry() && allow){
					if(tblLastEntryTimestamps.containsKey(si.getActionID())) {
						Timestamp lastRecordedTime = tblLastEntryTimestamps.get(si.getActionID());
						
						// If this time is < last time + 5 minutes, don't post update.
						Calendar previous = Calendar.getInstance();
						previous.setTime(lastRecordedTime);
						previous.add(Calendar.MINUTE, 5);
						Calendar current = Calendar.getInstance();
						current.setTime(si.getTime());
						if(current.before(previous)) {
							lg.log("# Event occured too recently prior to this.");
							allow = false;
						}
					} else {
						tblLastEntryTimestamps.put(si.getActionID(), si.getTime());
					}
				}
				
				// If allow is true.
				if(allow) {
					// Perform the update.
					if(si.isEventEntry()) {
						tblWaitingForEntry.put(si.getActionID(), Boolean.FALSE);
					} else {
						tblWaitingForEntry.put(si.getActionID(), Boolean.TRUE);
					}
					UpdateStringBuilder st = null;
					st = new UpdateStringBuilder(catName, si.getTime(),
							getTag(si.getActionID(),si.isEventEntry(),catMale),
							si.getSnapshotURL());
					
					lg.log("! Update pushed to social networks.");
					pm.pushUpdate(st.toSocialNetworkString());
					lg.increaseMessageCount();
					lg.setLastMessage(st.toGUIString());
					
					// If the event was an entry, contribute it to the stats.
					if(si.isEventEntry()) {
					stats.updateStats(si);
					}
				}
				// After this update, update the lastProcessedSightingID.
				if (this.lstProcSightTime.before(si.getTime())) {
					lstProcSightTime = si.getTime();
					lg.setTimeStamp(si.getTime());
					db.setLastProcessed(si.getTime().toString());
				}
				lg.increaseNumberOfSightingsProcessed();
			}
		}
		disconnect();
	}
	
	private String getTag(String actionID, boolean isEntry, boolean isMale) throws SQLException {
		List<String> al = db.getTags(actionID, isEntry, isMale);
		try{
		int random = (int)(Math.random()*al.size());
		return al.get(random);
		} catch (NullPointerException e) {
			throw new SQLException();
		}
	}
	
	public void refreshCredentials() throws SQLException {
		pm.clearPluginInstances();
		connect();
		
		List<Account> accountList = db.getAccounts();
		ListIterator<Account> itr = accountList.listIterator();

		while (itr.hasNext()) {
			Account currentAccount = itr.next();
			String accountType = currentAccount.getMediaID();
			
			if (accountType.equals("FCBK")) {
				lg.log("Media Registration - Facebook - Account ID:"
								+ currentAccount.getAccountID());
				FacebookPlugin p = new FacebookPlugin(currentAccount);
				pm.registerPluginInstance(p);
			}
			if (accountType.equals("TWTR")) {
				lg.log("Media Registration - Twitter - Account ID:"
						+ currentAccount.getAccountID());
				TwitterPlugin p = new TwitterPlugin(currentAccount);
				pm.registerPluginInstance(p);
			}
			if (accountType.equals("MAIL")) {
				lg.log("Media Registration - Email - Account ID:"
						+ currentAccount.getAccountID());
				EmailPlugin p = new EmailPlugin(currentAccount);
				pm.registerPluginInstance(p);
			}
			if(accountType.equals("PING")) {
				lg.log("Media Registration - PingFM - Account ID:" + currentAccount.getAccountID());
				PingFMPlugin p = new PingFMPlugin(currentAccount);
				pm.registerPluginInstance(p);
			}
		}
		disconnect();
	}
	
	public void sendTestMessage() {
		lg.log("Pushing a test message");
		pm.pushUpdate("This is a test message.");
	}
	
}
